﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using LuaInterface;
using Newtonsoft.Json;

namespace Framework
{
    public enum ResLoadType : int
    {
        /// <summary>
        /// 直接加载
        /// </summary>
        FromEditorDir = 0,
        /// <summary>
        /// 从资源库中加载
        /// </summary>
        FromAssetLib = 1,
    }
    
    /*
     * 移除一个资源的时候，只是移除回调，assetItem不清除，等GC的时候，统一清除
     * 加载资源失败的时候，需要直接清除assetItem
     */
    public partial class ResMgr : MonoSingleton<ResMgr>
    {
        /// <summary>
        /// 文件加载器
        /// </summary>
        private FileLoaderInterface iFileLoader;
        
        private bool _pauseLoadingWhenGC = true;
        /// <summary>
        /// 是否在GC的时候暂停掉加载
        /// </summary>
        /// <value></value>
        public bool PauseLoadingWhenGC
        {
            get => _pauseLoadingWhenGC;
            set => _pauseLoadingWhenGC = value;
        }
        
        /// <summary>
        /// 文件加载的最大协程数目
        /// </summary>
        /// <param name="maxCount">最大加载协程数</param>
        public void SetMaxFileLoadingCount(int maxCount)
        {
            iFileLoader.SetMaxLoadingCount(maxCount);
        }
        
        /// <summary>
        /// 文件加载失败的重试次数
        /// </summary>
        /// <param name="maxCount"></param>
        public void SetMaxFileLoadingRetryCount(int maxCount)
        {
            iFileLoader.SetFailRetryCount(maxCount);
        }
        
        /// <summary>
        /// 设置从Ab加载为UnityEngine.GameObject的最大携程数
        /// </summary>
        /// <param name="maxCount"></param>
        public void SetMaxAbLoadingCount(int maxCount)
        {
            AssetBundleLoader.Instance.SetMaxLoadingCount(maxCount);
        }
        
        /// <summary>
        /// 是否开启Unity的cache系统,默认不开启
        /// </summary>
        /// <param name="use"></param>
        public void SetUseUnityCacheSystem(bool use)
        {
            iFileLoader.SetUseUnityCaching(use);
        }
        
        /// <summary>
        /// 获取Unity默认cache系统的路径
        /// 主要是用来做删除某些cache的
        /// </summary>
        /// <returns></returns>
        public string GetUnityCachePath()
        {
            return Caching.defaultCache.path;
        }
        
        /// <summary>
        /// 是否启用cache系统的压缩,节省一下磁盘空间
        /// 默认是开启的
        /// </summary>
        /// <param name="useCompress"></param>
        public void SetCacheSystemCompress(bool useCompress)
        {
            Caching.compressionEnabled = useCompress;
        }
        
        /// <summary>
        /// 清理掉Unity的cache
        /// </summary>
        public void ClearUnityCache()
        {
            Caching.ClearCache();
        }

        /// <summary>
        /// 当前的加载模式,直接加载,还是从资源库中加载
        /// </summary>
        private ResLoadType _curLoadType;
        /// <summary>
        /// 是否是从编辑器目录下加载
        /// </summary>
        public bool IsFromEditorDir => _curLoadType == ResLoadType.FromEditorDir;
        
        private bool _useLuaBytes;

#if UNITY_EDITOR
        [LuaInterface.NoToLua]
        public Dictionary<string, AssetItem> assetCache => _assetCache;
#endif
        /// <summary>
        /// 资源缓存容器
        /// </summary>
        private Dictionary<string, AssetItem> _assetCache;
        /// <summary>
        /// ab的引用计数
        /// </summary>
        private Dictionary<string, int> _abRefCountTab;
        /// <summary>
        /// ab汇总信息文件
        /// </summary>
        private AllManifest _allMainifest;

        private bool useLuaBytes;
        /// <summary>
        /// 是否使用lua字节码
        /// </summary>
        /// <value>用/不用</value>
        public bool UseLuaBytes => useLuaBytes;
        /// <summary>
        /// 是否要加载lua的字节码
        /// </summary>
        /// <value></value>
        public bool NeedLoadLuaBytes => useLuaBytes && _curLoadType == ResLoadType.FromAssetLib;
        
        public override void Init(Action callback = null)
        {
            base.Init(callback);
            InitLoadType();
            _assetCache = new Dictionary<string, AssetItem>();
            iFileLoader = gameObject.AddComponent<FileLoader>();
            iFileLoader.Init();
            AssetBundleLoader.Instance.Init();
            InitAbDependencies(OnInitFinish);
            // if (_curLoadType == ResLoadType.FromAssetLib)
            // {
            //     iFileLoader = gameObject.AddComponent<FileLoader>();
            //     iFileLoader.Init();
            //     AssetBundleLoader.Instance.Init();
            // }
            // else
            // {
            //     iFileLoader = gameObject.AddComponent<EditorFileLoader>();
            //     iFileLoader.Init();
            // }
            // OnInitFinish();
        }
        
        private void InitLoadType()
        {
#if UNITY_EDITOR
            _curLoadType = Boot.Instance.loadType;
            useLuaBytes = false;
#else
            _curLoadType = ResLoadType.FromAssetLib;
            useLuaBytes = true;
#endif
            Logger.Log("ResMgr.InitLoadType, _curLoadType = " + _curLoadType + " useLuaBytes = " + _useLuaBytes);
        }

        public AssetItem GetPersistentResForLua(string resPath, int assetType, LuaFunction luaOnLoaded = null, LuaTable luaObj = null)
        {
            return GetPersistentRes(resPath, assetType, null, luaOnLoaded, luaObj);
        }
        public AssetItem GetPersistentRes(string resPath, int assetType, OnAssetLoaded onLoaded = null, LuaFunction luaOnLoaded = null, LuaTable luaObj = null)
        {
            resPath = FileHelper.GetUnityPath(resPath);
            if (string.IsNullOrEmpty(resPath))
            {
                Logger.LogError("ResMgr.GetPersistentRes, url can not be null or empty!");
                return null;
            }

            AssetItem ret = null;
            //cache中没有,则去加载
            if (!_assetCache.TryGetValue(resPath, out ret))
            {
                ret = AssetItem.GetNonAbAssetItem(resPath, assetType);
                ret.isPersistenUrl = true;
                ret.AddAllOnAssetLoadedCb(onLoaded, luaObj, luaOnLoaded);
                _assetCache[resPath] = ret;
                ret.Retain();
                iFileLoader.Load(ret);
                ResLogMgr.Instance.OnStartLoad(resPath);
                return ret;
            }
            //cache中不应该有空元素
            if (null == ret)
            {
                Logger.LogError("ResMgr.GetPersistentRes, _assetCache should not have null item! url = " + resPath);
                return null;
            }
            //如果加载完成了,则直接调用加载回调
            if (ret.IsLoadFinish)
            {
                if (null != onLoaded || null != luaOnLoaded)
                {
                    AssetCallBack assetCb = AssetCallBack.GetAssetCallBack(onLoaded, luaObj, luaOnLoaded);
                    assetCb.Invoke(ret);
                    AssetCallBack.RecyleAssetCallBack(assetCb);
                }
            }
            else
            {
                ret.AddAllOnAssetLoadedCb(onLoaded, luaObj, luaOnLoaded);
            }
            return ret;
        }
        
        /// <summary>
        /// 专门给lua使用的接口,获取非ab资源
        /// </summary>
        /// <param name="resPath"></param>
        /// <param name="assetType"></param>
        /// <param name="luaOnLoaded"></param>
        /// <param name="luaObj"></param>
        /// <returns></returns>
        public AssetItem GetNonAbAssetForLua(string resPath, int assetType, LuaFunction luaOnLoaded = null, LuaTable luaObj = null)
        {
            return GetNonAbAsset(resPath, assetType, null, luaOnLoaded, luaObj);
        }

        /// <summary>
        /// 获取非ab资源
        /// </summary>
        /// <param name="resPath">全路径</param>
        /// <param name="assetType">资源类型AssetType</param>
        /// <param name="onLoaded">C#的加载完成回调函数</param>
        /// <param name="luaObj">lua加载完成回调函数的对象</param>
        /// <param name="luaOnLoaded">lua加载完成回调函数</param>
        /// <returns></returns>
        [LuaInterface.NoToLua]
        public AssetItem GetNonAbAsset(string resPath, int assetType, OnAssetLoaded onLoaded = null, LuaFunction luaOnLoaded = null, LuaTable luaObj = null)
        {
            resPath = FileHelper.GetUnityPath(resPath);
            Logger.Log("GetNonAbAsset resPath = " + resPath);
            if (string.IsNullOrEmpty(resPath))
            {
                Logger.LogError("ResMgr.GetNonAbAsset, resPath can not be null or empty!");
                return null;
            }
            AssetItem ret = null;
            //cache中没有,则去加载
            if (!_assetCache.TryGetValue(resPath, out ret))
            {
                ret = AssetItem.GetNonAbAssetItem(resPath, assetType);
                ret.SetFullPath(resPath);
                ret.AddAllOnAssetLoadedCb(onLoaded, luaObj, luaOnLoaded);
                _assetCache[resPath] = ret;
                ret.Retain();
                iFileLoader.Load(ret);
                ResLogMgr.Instance.OnStartLoad(resPath);
                return ret;
            }
            //cache中不应该有空元素
            if (null == ret)
            {
                Logger.LogError("ResMgr.GetNonAbAsset, _assetCache should not have null item! url = " + resPath);
                return null;
            }
            //如果加载完成了,则直接调用加载回调
            if (ret.IsLoadFinish)
            {
                if (null != onLoaded || null != luaOnLoaded)
                {
                    AssetCallBack assetCb = AssetCallBack.GetAssetCallBack(onLoaded, luaObj, luaOnLoaded);
                    assetCb.Invoke(ret);
                    AssetCallBack.RecyleAssetCallBack(assetCb);
                }
            }
            else
            {
                ret.AddAllOnAssetLoadedCb(onLoaded, luaObj, luaOnLoaded);
            }
            return ret;
        }
        
        /// <summary>
        /// 给lua用的获取获取ab资源的接口
        /// </summary>
        /// <param name="resPath"></param>
        /// <param name="needLoadAsset"></param>
        /// <param name="luaOnLoaded"></param>
        /// <param name="luaObj"></param>
        /// <returns></returns>
        public AssetItem GetAbAssetForLua(string resPath, bool needLoadAsset = false, LuaFunction luaOnLoaded = null, LuaTable luaObj = null)
        {
            return GetAbAsset(resPath, needLoadAsset, null, luaOnLoaded, luaObj);
        }
        
        /// <summary>
        /// 获取ab资源
        /// </summary>
        /// <param name="resPath">ab资源路径</param>
        /// <param name="needLoadAsset">是否需要把ab中的资源加载出来</param>
        /// <param name="onLoaded">加载完成后的c#回调</param>
        /// <param name="luaOnLoaded">加载完成后的lua回调</param>
        /// <param name="luaObj">加载完成之后lua回调的对象</param>
        /// <returns>资源缓存单元</returns>
        [LuaInterface.NoToLua]
        public AssetItem GetAbAsset(string resPath, bool needLoadAsset = false, OnAssetLoaded onLoaded = null, LuaFunction luaOnLoaded = null, LuaTable luaObj = null)
        {
            resPath = FileHelper.GetUnityPath(resPath);
            if (string.IsNullOrEmpty(resPath))
            {
                Logger.LogError("ResMgr.GetAbAsset, resPath can not be null or empty!");
                return null;
            }
            //不存在缓存,则去加载
            if (!_assetCache.TryGetValue(resPath, out AssetItem ret))
            {
                ret = AssetItem.GetAbAssetItem(resPath, needLoadAsset);
                ret.AddAllOnAssetLoadedCb(onLoaded, luaObj, luaOnLoaded);
                _assetCache[resPath] = ret;
                ret.Retain();

                if (_curLoadType == ResLoadType.FromAssetLib)
                {
                    string abPath = EnsureAbPath(ret.AssetUrl);
                    // Logger.LogWarning("ResMgr.GetAbAsset resPath = " + resPath + " abPath = " + abPath);
                    string[] directDeps = GetAbDirectDeps(abPath);
                    if (null != directDeps)
                    {
                        foreach (string depPath in directDeps)
                        {
                            AssetItem assetItem = GetAbAsset(depPath);
                            ret.AddDependencyItem(assetItem);
                        }
                    }
                }
                iFileLoader.Load(ret);
                ResLogMgr.Instance.OnStartLoad(resPath);
                return ret;
            }
            //有资源缓存,并且加载好了
            if (ret.IsLoadFinish)
            {
                if (ret.IsLoadSuccess && needLoadAsset && (ret.EngineAsset == null))
                {
                    ret.AddAllOnAssetLoadedCb(onLoaded, luaObj, luaOnLoaded);
                    AssetBundleLoader.Instance.LoadAssetFromAb(ret, OnLoadAssetFromAb);
                }
                else
                {
                    AssetCallBack cb = AssetCallBack.GetAssetCallBack(onLoaded, luaObj, luaOnLoaded);
                    cb.Invoke(ret);
                    AssetCallBack.RecyleAssetCallBack(cb);
                }
                return ret;
            }
            //有资源缓存,但是没有加载好
            ret.NeedLoadAssetFromAb = needLoadAsset;
            ret.AddAllOnAssetLoadedCb(onLoaded, luaObj, luaOnLoaded);
            return ret;
        }

        /// <summary>
        /// 从缓存中的ab资源中拿引擎资源,完成后的回调
        /// </summary>
        /// <param name="assetItem"></param>
        /// <param name="engineObj"></param>
        private void OnLoadAssetFromAb(AssetItem assetItem, UnityEngine.Object engineObj)
        {
            assetItem.OnLoadedDone();
        }
        
        /// <summary>
        /// 确保ab的路径都是有ab后缀名字的
        /// </summary>
        /// <param name="abPath"></param>
        /// <returns></returns>
        internal string EnsureAbPath(string abPath)
        {
            abPath = FileHelper.GetUnityPath(abPath);
            return abPath.CustomEndsWith(FrameworkSettings.AssetBundleExt) ? abPath : abPath + FrameworkSettings.AssetBundleExt;
        }
        
        
        /// <summary>
        /// 获取某个ab的直接依赖
        /// </summary>
        /// <param name="abPath"></param>
        /// <returns>所有直接依赖资源路径</returns>
        internal string[] GetAbDirectDeps(string abPath)
        {
            abPath = FileHelper.GetUnityPath(abPath);
            if (null == _allMainifest)
            {
                return null;
            }
            abPath = EnsureAbPath(abPath);
            // Logger.Log("ResMgr.GetAbDirectDeps abPath = " + abPath);
            string[] directDeps = _allMainifest.GetDirectDependencies(abPath);
            List<string> ret = new List<string>();
            foreach (string dep in directDeps)
            {
                if (dep.CustomEndsWith(FrameworkSettings.PrefabBundleExt))
                {
                    //todo 验证是否能够存在prefab间引用
                    Logger.LogError($"ResMgr.GetAbDirectDeps, 不应该存在prefab引用prefab的情况,请注意!abPath = {abPath} dep = {dep}");
                    continue;
                }
                ret.Add(dep.Replace(FrameworkSettings.AssetBundleExt, ""));
            }
            return ret.ToArray();
        }
        
        /// <summary>
        /// 查询某个ab的引用次数
        /// </summary>
        /// <param name="abPath">ab路径</param>
        /// <returns></returns>
        internal int GetAbRefCount(string abPath)
        {
            abPath = FileHelper.GetUnityPath(abPath);
            if (null == _allMainifest)
            {
                return 0;
            }
            abPath = EnsureAbPath(abPath);
            if (_abRefCountTab.TryGetValue(abPath, out int ret))
            {
                return ret;
            }
            return 0;
        }
        
        /// <summary>
        /// 移除某个资源的lua回调函数,用于加载未完成但是,不想加载某个资源了
        /// </summary>
        /// <param name="resPath"></param>
        /// <param name="luaCb"></param>
        /// <param name="luaObj"></param>
        public void RemoveAssetItemCb(string resPath, LuaFunction luaCb, LuaTable luaObj)
        {
            resPath = FileHelper.GetUnityPath(resPath);
            if (string.IsNullOrEmpty(resPath))
            {
                return;
            }
            if (_assetCache.TryGetValue(resPath, out AssetItem assetItem))
            {
                assetItem.RemoveCallBack(luaCb, luaObj);
            }
        }

        /// <summary>
        /// 移除某个资源的lua回调函数,用于加载未完成但是,不想加载某个资源了
        /// </summary>
        /// <param name="resPath"></param>
        /// <param name="onAssetLoaded"></param>
        [LuaInterface.NoToLua]
        public void RemoveAssetItemCb(string resPath, OnAssetLoaded onAssetLoaded)
        {
            resPath = FileHelper.GetUnityPath(resPath);
            if (string.IsNullOrEmpty(resPath))
            {
                return;
            }

            if (_assetCache.TryGetValue(resPath, out AssetItem assetItem))
            {
                assetItem.RemoveCallBack(onAssetLoaded);
            }
        }

        /// <summary>
        /// 清除某个资源
        /// </summary>
        /// <param name="item"></param>
        public void ClearItem(AssetItem item)
        {
            if (item.IsDestroyed)
            {
                return;
            }
            //加载已完成,则判定是否被其他地方引用,有则不销毁,没有则立即销毁
            if (item.IsLoadFinish)
            {
                if (item.ReferenceCount > 1)
                {
                    Logger.LogWarning($"ResMgr.ClearItem, {item},  ReferenceCount > 1, can not be clear!");
                    return;
                }
                _assetCache.Remove(item.ResPath);
                iFileLoader.RemoveAsset(item);
                item.DestroySelf();
                AssetItem.Recycle(ref item);
                return;
            }
            //如果正在加载,则清理掉回调函数,但是不停掉加载
            if (iFileLoader.IsItemInLoading(item))
            {
                item.ClearAllHandlers();
                return;
            }
            //等待加载中,则直接移除掉就好
            _assetCache.Remove(item.ResPath);
            iFileLoader.RemoveAsset(item);
            item.DestroySelf();
            AssetItem.Recycle(ref item);
        }
        
        /// <summary>
        /// 某个资源加载失败了,则将其从cache中删除,并且给销毁了
        /// </summary>
        /// <param name="assetItem"></param>
        internal void OnLoadedFailed(AssetItem assetItem)
        {
            ClearItem(assetItem);
        }

        
        private List<AssetItem> toBeClearAssets = new List<AssetItem>();
        private System.Text.StringBuilder clearedPath = new System.Text.StringBuilder();
        private bool isInGC = false;
        /// <summary>
        /// GC操作
        /// </summary>
        public void ClearUnusedAssets()
        {
            if (isInGC)
            {
                Logger.LogWarning("ResMgr.ClearUnusedAssets, GC is running, ignore this call. isInGC = true!");
                return;
            }
            StartCoroutine(DoClearUnusedAssets());
        }

        private IEnumerator DoClearUnusedAssets()
        {
            isInGC = true;
            if (UnityHelper.IsDebugBuild())
            {
                TimeWatch.Instance.Start();
                clearedPath.Clear();
            }
            toBeClearAssets.Clear();
            toBeClearAssets.AddRange(_assetCache.Values);

            //确保清理一个资源之后,其依赖引用计数减少后,依赖资源也能被销毁
            bool needContinueClear = true;
            int clearCount = 0;
            int allListCount = 0;
            while (needContinueClear)
            {
                allListCount++;
                needContinueClear = false;
                for (int idx = toBeClearAssets.Count - 1; idx >= 0; idx--)
                {
                    AssetItem item = toBeClearAssets[idx];
                    if (item.ReferenceCount <= 1 && !item.IsDestroyed)
                    {
                        if (UnityHelper.IsDebugBuild())
                        {
                            clearedPath.AppendFormat("\t{0}\n", item.AssetUrl);
                        }
                        ClearItem(item);
                        toBeClearAssets.RemoveAt(idx);
                        needContinueClear = true;
                        clearCount++;
                    }
                }
            }
            toBeClearAssets.Clear();

            if (_pauseLoadingWhenGC)
            {
                iFileLoader.SetPause(true);
                if (iFileLoader.IsLoading())
                {
                    yield return null;
                }
            }
            yield return Resources.UnloadUnusedAssets();
            if (_pauseLoadingWhenGC)
            {
                iFileLoader.SetPause(false);
            }
            yield return new WaitForEndOfFrame();
            UnityHelper.CsharpGC();
            if (UnityHelper.IsDebugBuild())
            {
                Logger.Log($"ResMgr.DoClearUnusedAssets, cost time: {TimeWatch.Instance.Watch()} s, clearCount: {clearCount}, allListCount: {allListCount}, clearedPath:\n{clearedPath}");
                clearedPath.Clear();
            }
            isInGC = false;
        }
    }
}